package com.huitongjy.common.generator;

import java.util.Calendar;

/**
 * <p>
 *      Use snowflake algorithm. Length is 64 bit.
 * </p>
 *
 * <pre>
 * 1bit   符号位 不使用
 * 41bits 时间戳
 * 10bit 机器Id
 * 12bit 自增序列
 * </pre>
 *
 * @author guan
 */
public class SimpleKeyGenerator implements IKeyGenerator {
    /**
     * 时间位(41bit) 初始时间(毫秒) 2017.11.01
     */
    public static final long EPOCH;

    static {
        Calendar calendar = Calendar.getInstance();
        calendar.set(2017, Calendar.NOVEMBER, 1);
        calendar.set(Calendar.HOUR_OF_DAY, 0);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        calendar.set(Calendar.MILLISECOND, 0);
        EPOCH = calendar.getTimeInMillis();
    }

    /**
     * 自增序列位 12 (4096)
     */
    private int sequenceBits = 12;

    /**
     * 机器位 10 (1024)
     */
    private int workerIdBits = 10;

    /**
     * 自增序列位限制位 通过与操作确保不会大于12位(4095)
     */
    private int sequenceMask = (1 << sequenceBits) - 1;

    /**
     * 机器Id偏移
     */
    private int workerIdLeftShiftBits = sequenceBits;

    /**
     * 时间戳偏移
     */
    private int timestampLeftShiftBits = workerIdLeftShiftBits + workerIdBits;

    /**
     * 机器Id最大值
     */
    private long workerIdMaxValue = 1L << workerIdBits;

    private long sequence;

    private long lastTime;

    private long workerId;

    public SimpleKeyGenerator(long workerId) {
        if(workerId < 0L && workerId >= workerIdMaxValue) {
            throw new RuntimeException("机器ID过大");
        }
        this.workerId = workerId;
    }

    public SimpleKeyGenerator(int workerIdBits, int sequenceBits, long workerId) {
        if (workerIdBits + sequenceBits > 22) {
            throw new RuntimeException("生成器位数溢出");
        }

        this.sequenceBits = sequenceBits;
        this.sequenceMask = (1 << sequenceBits) - 1;
        this.workerIdLeftShiftBits = sequenceBits;

        this.workerIdBits = workerIdBits;
        this.timestampLeftShiftBits = workerIdLeftShiftBits + workerIdBits;
        this.workerIdMaxValue = 1L << workerIdBits;

        if(workerId < 0L && workerId >= workerIdMaxValue) {
            throw new RuntimeException("机器ID过大");
        }
        this.workerId = workerId;
    }

    /**
     * Generate key.
     *
     * @return key type is @{@link Long}.
     */
    @Override
    public synchronized Long generateKey() {
        long currentMillis = this.timeGen();
        if(currentMillis<lastTime) {
            //时间回拨
            currentMillis = waitLastTime(currentMillis);
        } else if (lastTime == currentMillis) {
            if (++sequence > sequenceMask) {
                sequence = 0;
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = 0;
        }

        lastTime = currentMillis;
        return buildGenerateKey(currentMillis, sequence);
    }


    /**
     * batch generate key.
     *
     * @return key type is @{@link Long}.
     */
    @Override
    public synchronized BatchKey generateKey(int num) {
        long currentMillis = this.timeGen();
        if(currentMillis<lastTime) {
            //时间回拨
            currentMillis = waitLastTime(currentMillis);
        } else if (lastTime == currentMillis) {
            if (sequence == sequenceMask) {
                sequence = -1;
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } else {
            sequence = -1;
        }

        BatchKey batchKey = new BatchKey();
        do {
            //当前索引
            long begin = buildGenerateKey(currentMillis, sequence + 1);
            //当前毫秒剩余索引数
            long timeSize = sequenceMask - sequence;
            if (num <= timeSize) {
                //当前毫秒足够
                batchKey.add(new BatchKey.Item(begin, num));
                sequence += num;
                break;
            } else {
                batchKey.add(new BatchKey.Item(begin, (int) timeSize));
                num -= timeSize;
                //等待下一毫秒
                sequence = -1;
                currentMillis = waitUntilNextTime(currentMillis);
            }
        } while (num > 0);

        lastTime = currentMillis;
        return batchKey;
    }

    protected long buildGenerateKey(long currentMillis, long sequence) {
        return ((currentMillis - EPOCH) << timestampLeftShiftBits) | (workerId << workerIdLeftShiftBits) | sequence;
    }

    private long waitUntilNextTime(final long lastTime) {
        long time = this.timeGen();
        while (time <= lastTime) {
            time = this.timeGen();
        }
        return time;
    }

    /**
     * 处理时间回拨
     * @param time
     * @return
     */
    private long waitLastTime(long time) {
        long offset = lastTime - time;
        if (offset <= 5) {
            //时间偏差大小小于5ms，则等待两倍时间
            try {
                wait(offset << 1);
            } catch (InterruptedException e) {
                throw new RuntimeException("时间回拨");
            }
            time = timeGen();
            if (time < lastTime) {
                //还是小于，抛异常并上报
                throw new RuntimeException("时间回拨");
            }
        } else {
            throw new RuntimeException("时间回拨");
        }
        return time;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}
